﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using EvidenceManagement.Model.Security;

namespace EvidenceManagement.Logic.Security
{
    public static class SecurityPolicyManager
    {
        static Dictionary<string, SecurityPolicy> SecurityPolicies = new Dictionary<string, SecurityPolicy>();

        /// <summary>
        /// Creates the a security policy.
        /// </summary>
        /// <param name="policyName">Name of the security policy to create.</param>
        /// <returns></returns>
        public static SecurityPolicy CreatePolicy(string policyName)
        {
            if (!SecurityPolicies.ContainsKey(policyName))
                SecurityPolicies.Add(policyName, new SecurityPolicy(policyName));

            return SecurityPolicies[policyName];
        }

        public static void AddModule(SecurityPolicy securityPolicy, Type secureType)
        {
            if (string.IsNullOrEmpty(securityPolicy.PolicyName)
                || !SecurityPolicies.ContainsKey(securityPolicy.PolicyName))
            {
                throw new SecurityPolicyNotInitializedException(securityPolicy.PolicyName);
            }

            object[] moduleAttribute = secureType.GetCustomAttributes(typeof(AccessControlModule), false);

            if (moduleAttribute == null || moduleAttribute.Length == 0 || !(moduleAttribute[0] is AccessControlModule))
                throw new IncompatibleModuleException(secureType);

            PropertyInfo[] properties = secureType.GetProperties();

            PropertyInfo[] filteredActionFields = properties.Where(p => p.GetCustomAttributes(typeof(AccessControlAction), false).Length == 1).ToArray();

            List<MonitoredAction> monitoredActions = new List<MonitoredAction>();

            if (filteredActionFields == null || filteredActionFields.Length == 0)
                throw new UndifinedActionsException(secureType);

            var enumerator = filteredActionFields.GetEnumerator();
            while (enumerator.MoveNext())
            {
                var action = ((enumerator.Current as PropertyInfo).GetCustomAttributes(typeof(AccessControlAction), false)[0] as AccessControlAction);
                monitoredActions.Add(new MonitoredAction(action.ActionName, ActionPermission.Deny));
            }

            AccessControlModule secureModule = moduleAttribute[0] as AccessControlModule;
            var monitoredModule = new MonitoredModule(secureModule.ModuleName, secureType);

            monitoredModule.AddActionList(monitoredActions);

            SecurityPolicies[securityPolicy.PolicyName].AddModule(monitoredModule);

        }
        
        public static void ApplyPolicy(string policyXML, object presenter)
        {
            SecurityPolicy policy = SecurityPolicyManager.GetPolicyFromXML(policyXML);
            Type presenterType = presenter.GetType();

            MonitoredModule monitoredModule = policy.MonitoredModules.FirstOrDefault(m => m.TypeName == presenter.GetType().FullName);

            if (monitoredModule != null)
            {
                if (monitoredModule.MonitoredActions.Length <= 0)
                    return;

                object[] moduleAttribute = presenterType.GetCustomAttributes(typeof(AccessControlModule), false);

                if (moduleAttribute == null || moduleAttribute.Length == 0 || !(moduleAttribute[0] is AccessControlModule))
                    return;

                PropertyInfo[] properties = presenterType.GetProperties();

                PropertyInfo[] actionProperties = properties.Where(p => p.CanWrite && p.GetCustomAttributes(typeof(AccessControlAction), false).Length == 1).ToArray();

                if (actionProperties == null || actionProperties.Length == 0)
                    return;

                SecurityPolicyManager.SetPropertyValues(presenter, presenterType, actionProperties, monitoredModule);
            }
        }

        private static SecurityPolicy GetPolicyFromXML(string policyXML)
        {
            var deserializer = new XmlSerializer(typeof(SecurityPolicy));
            var xml = new StringBuilder(policyXML);
            SecurityPolicy securityPolicy = null;

            using (StringReader stringReader = new StringReader(policyXML))
            {
                using (var xmlReader = new XmlTextReader(stringReader))
                {                 
                    securityPolicy = (deserializer.Deserialize(xmlReader) as SecurityPolicy);
                }
            }
            
            return securityPolicy;
        }

        private static void SetPropertyValues(object presenter,Type presenterType, PropertyInfo[] actionProperties, MonitoredModule monitoredModule)
        {
            var enumerator = actionProperties.GetEnumerator();
            while (enumerator.MoveNext())
            {
                var action = ((enumerator.Current as PropertyInfo).GetCustomAttributes(typeof(AccessControlAction), false)[0] as AccessControlAction);

                var monitoredAction = monitoredModule.MonitoredActions.FirstOrDefault(a => a.ActionName == action.ActionName);
                if (monitoredAction != null)
                {
                    presenterType.GetProperty((enumerator.Current as PropertyInfo).Name).SetValue(presenter, monitoredAction.PermissionType, null);
                }
            }
        }

        private static void LoadSecurityPolicies(string[] policiesXML)
        {
            foreach (string xml in policiesXML)
            {
                SecurityPolicy policy = SecurityPolicyManager.GetPolicyFromXML(xml);

                if (SecurityPolicies.ContainsKey(policy.PolicyName))
                    SecurityPolicies.Add(policy.PolicyName, policy);
            }
        }
    }
}
